using System;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;

namespace PocketPC_AdventureGame
{
    public class GameObject
    {

		private class State 
		{
			public State( String _name, Bitmap _sprite ) 
			{
				name = _name;
				sprite = _sprite;
			}
			public String name;
			public Bitmap sprite;
		}

		private class Interaction {
            /// <summary>
            /// state change interaction
            /// </summary>
            /// <param name="_game"></param>
            /// <param name="sourceObj"></param>
            /// <param name="actorObj"></param>
            /// <param name="_state"></param>
            /// <param name="_consumeInteractionObject"></param>
			public Interaction( GameActions _game, GameObject sourceObj, GameObject actorObj, String _state, bool _consumeInteractionObject ) 
			{
				game = _game;
				sourceObject = sourceObj;
				actorObject = actorObj;
				actorStateName = _state;
				consumeSourceObject = _consumeInteractionObject;
				type = types.STATECHANGE;
			}

            /// <summary>
            /// text display interaction
            /// </summary>
            /// <param name="_game"></param>
            /// <param name="sourceObj"></param>
            /// <param name="_message"></param>
			public Interaction( GameActions _game, GameObject sourceObj, String _message ) 
			{
				game = _game;
				sourceObject = sourceObj;
				message = _message;
				type = types.TEXT;
				consumeSourceObject = false;
			}

            /// <summary>
            /// Exit interaction
            /// </summary>
            /// <param name="_game"></param>
            /// <param name="sourceObj"></param>
            /// <param name="_message"></param>
            public Interaction(GameActions _game, Player player, Exit targetEntrance)
            {
                game = _game;
                sourceObject = player;
                actorObject = targetEntrance;
                type = types.EXIT;
                consumeSourceObject = false;
            }
            
            public bool DoInteraction() 
			{
				switch(type) 
				{
					case(types.STATECHANGE):
						actorObject.SetState( actorStateName );
						break;
					case(types.TEXT):
						game.ShowText(message);
						break;
                    case (types.EXIT):
                        game.SwitchScreen((Exit)actorObject);
                        break;
                }
				return consumeSourceObject;
			}

			private enum types
			{
				STATECHANGE,
				TEXT,
				EXIT
			}
			private types type;

			private GameActions game;
			private string message;
			public GameObject sourceObject;
			private GameObject actorObject;
			private String actorStateName;
			private bool consumeSourceObject;
		}

        public bool notifyOfCollisions = false;
		public bool doPixelPerfectCollision = false;
        public Rectangle position;
		private Rectangle goalPosition;
		protected ImageAttributes attr = new ImageAttributes();
        private Color transparencyColor = Color.Black;
		public bool pickable = true;
		public readonly int originalWidth;
		public readonly int originalHeight;
		public float speed = 400.0f;
		public String name;

		private Hashtable states = new Hashtable();
		private State currentState;
		private Hashtable interactions = new Hashtable();

        public GameObject(String _name, Bitmap _sprite)
        {
			AddState("Default", _sprite);
			AddState("Moving", _sprite);
			SetState("Default");
			name = _name;
			originalWidth = _sprite.Width;
			originalHeight = _sprite.Height;
			position = new Rectangle(0, 0, originalWidth, originalHeight);
			goalPosition = position;
            attr.SetColorKey(transparencyColor, transparencyColor);
        }


        public virtual void Update(double timeSinceLastUpdate)
        {
			//get interactions to perfom when no others are happening
			ArrayList noneInters = (ArrayList)interactions["None"];
			if( noneInters != null ) DoInteractions(noneInters);

			//move (and scale) object
			if( currentState == states["Moving"] ) 
			{
				double xDir = goalPosition.X - position.X;
				double yDir = goalPosition.Y - position.Y;

				//normalize
				double distance = Math.Sqrt( (xDir*xDir)+(yDir*yDir) );
				xDir = xDir / distance;
				yDir = yDir / distance;

				double updatesTilGoal = Math.Max( distance/(speed*timeSinceLastUpdate), 1 );

				//check if arrived
				if( updatesTilGoal > 1 ) 
				{
					//move
                    position.X += (int)((xDir * speed * timeSinceLastUpdate) + 0.5f);
                    position.Y += (int)((yDir * speed * timeSinceLastUpdate) + 0.5f);

					//scale
					double dW = goalPosition.Width - position.Width;
					double dH = goalPosition.Height - position.Height;
					position.Width += (int)( (dW/updatesTilGoal) );
					position.Height += (int)( (dH/updatesTilGoal) );
				}
				else 
				{
					position = goalPosition;
					OnArrivedAtGoal();
				}
			}
        }

        /// <summary>
        /// Draws the object at its current position, using the current states bitmap
        /// </summary>
        /// <param name="g"></param>
        public virtual void Draw(Graphics g)
        {
            g.DrawImage(currentState.sprite, position, 0, 0, currentState.sprite.Width, currentState.sprite.Height, GraphicsUnit.Pixel, attr);
        }

		/// <summary>
		/// Called when a moving object has arrived at goal position.
		/// Default action is to go to "Default" state.
		/// </summary>
		protected virtual void OnArrivedAtGoal()
		{
			SetState("Default");
		}

        /// <summary>
        /// Do collision test. Check first bounding box, then pixels.
        /// Interested objects are notified.
        /// </summary>
        /// <returns>true if a collision was made, false otherwise</returns>
        /// <param name="collidingObject"></param>
        public bool Collide(GameObject collidingObject)
        {
            //first check bounding box
            if ((notifyOfCollisions || collidingObject.notifyOfCollisions) && position.IntersectsWith(collidingObject.position))
            {
                //if intersecting, do pixel collision test if requested
                if (!doPixelPerfectCollision || pixelCollision(collidingObject))
                {
                    //notify objects that are interested
                    if (notifyOfCollisions)
                    {
                        OnCollisionWith(collidingObject);
                    }
                    if (collidingObject.notifyOfCollisions)
                    {
                        collidingObject.OnCollisionWith(this);
                    }
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Check pixels in colliding objects for collision.
        /// </summary>
        /// <param name="collidingObject"></param>
        /// <returns></returns>
        private bool pixelCollision(GameObject collidingObject)
        {
            Rectangle intersection = position;
            intersection.Intersect(collidingObject.position);

            int offsetX1 = intersection.X - position.X;
            int offsetX2 = intersection.X - collidingObject.position.X;
            int offsetY1 = intersection.Y - position.Y;
            int offsetY2 = intersection.Y - collidingObject.position.Y;

            for (int y = 0; y < intersection.Height; y++)
            {
                for (int x = 0; x < intersection.Width; x++)
                {
                    if (currentState.sprite.GetPixel(offsetX1 + x, offsetY1 + y) != transparencyColor && collidingObject.currentState.sprite.GetPixel(offsetX2 + x, offsetY2 + y) != transparencyColor)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Checks if the goven coordinates contain a pixel color other than transparent.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public bool HitTest(int x, int y)
        {
            //check first if inside bounding box
            if (x >= position.Left && x <= position.Right && y >= position.Top && y <= position.Bottom)
            {
                if (currentState.sprite.GetPixel(x - position.Left, y - position.Top) != transparencyColor)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Called if a collosion is detected and variable notifyOfCollisions=true. Must be overrided in sub class.
        /// </summary>
        /// <param name="collidingObject"></param>
        protected virtual void OnCollisionWith(GameObject collidingObject)
        {
            Interact(collidingObject);
        }

        /// <summary>
        /// Sets the goal position and sets the object in the moving state
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
		public virtual void MoveTo( int x, int y ) 
		{
			goalPosition.X = x;
			goalPosition.Y = y;
			SetState("Moving");
		}

        /// <summary>
        /// Sets the goal position and sets the object in the moving state
        /// </summary>
        /// <param name="rect"></param>
        public virtual void MoveTo( Rectangle rect) 
		{
			goalPosition = rect;
			SetState("Moving");
		}

		/// <summary>
		/// Switches state to the new state, if it is not the current one.
		/// </summary>
		/// <param name="stateName"></param>
		public void SetState( String stateName ) 
		{
			State s = (State)states[stateName];
			if( s != null && (currentState == null || !currentState.name.Equals(stateName) ) )
			{
				currentState = s;
			}
		}

        /// <summary>
        /// Adds a new state (with bitmap) to this GameObject
        /// </summary>
        /// <param name="stateName"></param>
        /// <param name="_sprite"></param>
		public void AddState( String stateName, Bitmap _sprite ) 
		{
			states[stateName] = new State(stateName, _sprite );
		}

        /// <summary>
        /// Add a "none" Interaction to this GameObject. This is the interaction that is performed when no
        /// other objects interact with it (can be used to make an object allways go to 
        /// a given state when nothing touches it for example.
        /// </summary>
        /// <param name="game"></param>
        /// <param name="destObj"></param>
        /// <param name="destState"></param>
		public void AddNoneInteraction( GameActions game, GameObject destObj, String destState) 
		{
			Interaction inter = new Interaction(game, null, destObj, destState, false);
			ArrayList inters = (ArrayList)interactions["None"];
			if( inters == null  ) 
			{
				inters = new ArrayList();
			}
			inters.Add(inter);
			interactions["None"] = inters;
		}
		
        /// <summary>
        /// Add an interaction to this object.
        /// </summary>
        /// <param name="game"></param>
        /// <param name="interactionObj">The object touching this object that couses the interaction</param>
        /// <param name="destObj">The object to affect, this could be this object but doesn't have to be.</param>
        /// <param name="destState">The state to switch the destination object to.</param>
        /// <param name="consume"></param>
        public void AddStateChangeInteraction(GameActions game, GameObject interactionObj, GameObject destObj, String destState, bool consume)
        {
            AddInteraction( new Interaction(game, interactionObj, destObj, destState, consume) );
        }

        private void AddInteraction( Interaction inter ) 
        {
            ArrayList inters = (ArrayList)interactions[inter.sourceObject.name];
			if( inters == null  ) 
			{
				inters = new ArrayList();
			}
			inters.Add(inter);
            interactions[inter.sourceObject.name] = inters;
            notifyOfCollisions = true;

		}

        public void AddExitInteraction(GameActions game, Player interactionObj, Exit destObj)
        {
            AddInteraction(new Interaction(game, interactionObj, destObj));
        }


        /// <summary>
        /// Executes the interaction between this object and the specified object
        /// </summary>
        /// <param name="sourceObj"></param>
        /// <returns></returns>
		public bool Interact( GameObject sourceObj) 
		{
			ArrayList inters = (ArrayList)interactions[sourceObj.name];
			return DoInteractions(inters);
		}

        /// <summary>
        /// Utility method. Execute all interactions for this object. Called when an interaction has been detected.
        /// </summary>
        /// <param name="inters"></param>
        /// <returns></returns>
		private bool DoInteractions(ArrayList inters) 
		{
			bool interacted = false;
			if( inters != null ) 
			{
				foreach( Interaction inter in inters )
				{
					inter.DoInteraction();
					interacted = true;
				}
			}
			return interacted;
		}

    }
}
